﻿using System;
using System.IO;
using System.Threading.Tasks;
using Red.Core.Network.PacketNetwork;

namespace Red.CommUtils.TcpMessageProtocol
{
    public class ChannelConnection : IChannelConnection, IPacketConnectionListener
    {
        public const int INVALID_ID = -1;

        public event OnPacketEventHandler OnPacketEvent;
        private PacketNetworkManager _network;
        private int _connectionID = INVALID_ID;
        private int _listenerID = INVALID_ID;
        private CommAddress _address;

        public bool HasConnection => _connectionID != INVALID_ID;
        public bool IsListening => _listenerID != INVALID_ID;
        public bool IsOpened => HasConnection || IsListening;
        public ulong TotalBytesSent => _network.GetTotalBytesSentForConnection( _connectionID );

        public ulong TotalBytesReceived
        {
            get
            {
                ulong result = 0;
                if( HasConnection )
                    result += _network.GetTotalBytesReceivedForConnection( _connectionID );
                if ( IsListening )
                    result += _network.GetTotalBytesReceivedForListener( _listenerID );
                return result;
            }
        }

        public ChannelConnection( PacketNetworkManager network )
        {
            _network = network;
        }

        ~ChannelConnection()
        {
            Disconnect();
        }

        public void Send( MemoryStream data )
        {
            _network.Send( _connectionID, data );
        }

        public void OnClosed()
        {
            _connectionID = -1;
            _listenerID = -1;

            Disconnected?.Invoke();
        }

        public bool OnPacket( Stream packet, int remotePacketConnectionID )
        {
            if ( OnPacketEvent != null )
                return OnPacketEvent( packet, new TcpResponseFactory( remotePacketConnectionID, _network ) );

            return false;
        }

        public async Task<bool> ConnectTo( CommAddress address, int timeoutInMilliseconds, int retryDelay = 2000 )
        {
            // TODO: remove connection if trying to connect to another address
            return await Task.Run<bool>( async () =>
            {
                DateTime startTime = DateTime.Now;
                TimeSpan timeSpan = DateTime.Now - startTime;

                int packetConnectionID = 0;
                do
                {
                    packetConnectionID = await _network.CreateConnection( address, this );

                    if ( packetConnectionID == 0 && retryDelay > 0 )
                        await Task.Delay( retryDelay );

                    timeSpan = DateTime.Now - startTime;
                } while ( packetConnectionID == 0 && timeSpan.TotalMilliseconds < timeoutInMilliseconds );

                if ( packetConnectionID == 0 )
                    return false;

                _connectionID = packetConnectionID;
                _address = address;

                return true;
            } );
        }

        public async Task<bool> TryReconnect( int timeoutInMilliseconds )
        {
            if ( _address == null )
                return false;

            return await ConnectTo( _address, timeoutInMilliseconds );
        }

        public bool StartListening( CommAddress localAddress )
        {
            int packetListenerID = _network.CreateListener( localAddress, this );
            if ( packetListenerID == 0 )
                return false;

            _listenerID = packetListenerID;
            return true;
        }

        public void Disconnect()
        {
            if( HasConnection )
            {
                _network.CloseConnection( _connectionID );
                _connectionID = INVALID_ID;
            }

            if( IsListening )
            {
                _network.CloseListener( _listenerID );
                _listenerID = INVALID_ID;
            }
        }

        public event DisconnectedHandler Disconnected;
    }
}
